package org.example.orm;


import org.example.domain.User;
import org.example.orm.annotation.Delete;
import org.example.orm.annotation.Insert;
import org.example.orm.annotation.Select;
import org.example.orm.annotation.Update;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

/**
 * @author hsc
 * @date 2024-02-02
 * @Description 负责帮DAO来做之前所有读写的事情
 * 未来这个类中的方法处理纯粹的JDBC，DAO不处理了
 */
public class SqlSession {

    //专门负责处理类型分析等等的问题
    private Handler handler = new Handler();
    //数据库连接配置
    //驱动
    private String driverClass = ConfigurationReader.getValue("driverClass");
    //连接url
    private String url = ConfigurationReader.getValue("url");
    //用户名
    private String username = ConfigurationReader.getValue("username");
    //密码
    private String password = ConfigurationReader.getValue("password");

    /**
     * 查询一条数据方式一，按照策略模式的方式，实现返回值的组装rs---obj
     *
     * @param sql
     * @param rowMapper
     * @param values    查询条件，可以传递多个，动态数组，必须放在最后一个
     * @return
     */
    public <T> T selectOne(String sql, RowMapper rowMapper, Object... values) {
        //直接调用查询所有的获取第一个
        return (T) this.selectList(sql, rowMapper, values).get(0);
    }

    /**
     * 查询一条数据方式二：通过反射分析用户提供的类型，底层通过反射创建对象，反射组装
     *
     * @param sql
     * @param object     传递过来的参数用对象来存储
     * @param resultType
     * @param <T>
     * @return
     */
    public <T> T selectOne(String sql, Object object, Class resultType) {
        //直接从集合中拿一条
        return (T) this.selectList(sql, object, resultType).get(0);
    }

    /**
     * 查询所有数据方式一，按照策略模式的方式，实现返回值的组装rs---obj
     *
     * @param sql
     * @param rowMapper
     * @param values    传递过来的查询添加真实数据
     * @param <T>
     * @return
     */
    public <T> List<T> selectList(String sql, RowMapper rowMapper, Object... values) {
        //声明一个结果集
        List<T> list = new ArrayList<>();
        try {
            //加载驱动
            Class.forName(driverClass);
            //建立连接
            Connection connection = DriverManager.getConnection(url, username, password);
            //执行sql预处理
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            //设置sql对应的参数
            for (int i = 0; i < values.length; i++) {
                //select * from users where uid = ? and username=?
                preparedStatement.setObject(i + 1, values[i]);
            }
            //执行查询
            ResultSet resultSet = preparedStatement.executeQuery();
            //将结果集resultSet中的数据 封装到新的实体中返回
            while (resultSet.next()) {
                //如果还有下一个条数据
                //由于sqlSession对象在构建domain的时候不确定到底构建什么类型
                //需要一个条件，让不同的DAO对象告诉我，该如何处理对象
                T object = rowMapper.mapperRow(resultSet);
                list.add(object);
            }
            //关闭资源
            resultSet.close();
            preparedStatement.close();
            connection.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //返回结果集
        return list;
    }

    /**
     * 查询所有数据方式二：通过反射分析用户提供的类型，底层通过反射创建对象，反射组装
     *
     * @param sql
     * @param object
     * @param resultType
     * @param <T>
     * @return
     */
    private <T> List<T> selectList(String sql, Object object, Class resultType) {
        List<T> resultList = new ArrayList<>();
        //获取原sql中的原始语句已经传递过来的占位符名称
        //获取到原始的sql :select *  from where uid=?
        //获取列值（列值跟对象的属性值对应）
        SQLAndKey sqlAndKey = handler.parseSQL(sql);
        //获取驱动
        try {
            Class.forName(driverClass);
            //获取连接
            Connection connection = DriverManager.getConnection(url, username, password);
            PreparedStatement preparedStatement = connection.prepareStatement(sqlAndKey.getSql());
            //根据传递过来的object查询条件，处理相应的数据，不限制任何类型 将对应的
            if (object != null) {
                handler.handleParameter(preparedStatement, object, sqlAndKey.getKeyList());
            }
            //preparedStatement对应的参数设置好
            //执行查询
            ResultSet resultSet = preparedStatement.executeQuery();
            //将rs里面的数据取出，存入一个新的容器里(domain)
            while (resultSet.next()) {
                T result = (T) handler.handleResult(resultSet, resultType);
                resultList.add(result);
            }
            resultSet.close();
            //关闭资源
            preparedStatement.close();
            //关闭连接
            connection.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultList;
    }

    /**
     * 添加数据方式一：使用动态数组传递参数
     *
     * @param sql
     * @param values
     */
    public void insert(String sql, Object... values) {
        this.updateUser(sql, values);
    }

    /**
     * 删除数据的方式一：使用动态参数传递sql参数
     *
     * @param sql
     * @param values
     */
    public void deleteUser(String sql, Object... values) {
        //直接调用修改的方法，可以直接使用
        this.updateUser(sql, values);
    }

    /**
     * 修改数据方式一：使用动态数组传递参数
     *
     * @param sql
     * @param values
     */
    public void updateUser(String sql, Object... values) {
        //加载驱动
        try {
            Class.forName(driverClass);
            //建立连接
            Connection connection = DriverManager.getConnection(url, username, password);
            //设置sql语句
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            //设置参数
            for (int i = 0; i < values.length; i++) {
                preparedStatement.setObject(i + 1, values[i]);
            }
            //执行语句
            preparedStatement.executeUpdate();
            //关闭连接
            preparedStatement.close();
            connection.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 修改数据方式二：使用对象传递sql的参数，替换sql中的？号值
     *
     * @param sql
     * @param object
     */
    public void updateUser(String sql, Object object) {
        try {
            //insert into users values(#{uid},#{uname},#{upass})
            //1.解析这条特殊规则的sql
            //      获取里面所有的#{key}   所有的key记录下来(按顺序)
            //      将#{key}东西全部删掉，替换回原来的问号------>还原SQL
            SQLAndKey sqlAndKey = handler.parseSQL(sql);
            //2.正常的JDBC流程
            Class.forName(driverClass);
            //3.让pstat对象，将带有问号的sql和obj对象中存储的值，进行完整的拼接
            Connection connection = DriverManager.getConnection(url, username, password);
            PreparedStatement preparedStatement = connection.prepareStatement(sqlAndKey.getSql());
            //将传进来的参数实际值替换sql的值
            if (object != null) {
                handler.handleParameter(preparedStatement, object, sqlAndKey.getKeyList());
            }
            //4.执行操作
            preparedStatement.executeUpdate();
            //5、关闭
            preparedStatement.close();
            ;
            connection.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 添加数据方式二：使用对象传递sql的参数
     *
     * @param sql
     * @param user
     */
    public void insert(String sql, User user) {
        this.updateUser(sql, user);
    }

    /**
     * 删除数据的方法方式二：使用对象传递sql的参数
     *
     * @param sql
     * @param object
     */
    public void deleteUser(String sql, Object object) {
        //直接调用修改的方法，可以直接使用
        this.updateUser(sql, object);
    }

    /**
     * 设计一个方法，可以在SqlSession类中产生一个代理对象---代替原来的DAO做事
     *
     * @param clazz 代替的是哪个DAO   UserDao.class
     * @param <T>
     * @return 返回一个代理对象    理解为是UserDao的代理(UserDAO的子类)
     */
    public <T> T getMapper(Class clazz) {
        //1.类加载器ClassLoader
        ClassLoader classLoader = clazz.getClassLoader();
        //2.加载的类Class[]     通常来说，数组里面就一个Class
        Class[] interfaces = new Class[]{clazz};
        //3.具体该如何做事InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler() {
            //invoke方法就是代理做事的方法     service调用dao的时候，代理就帮助dao做事
            //参数1-proxy(代理对象)   参数2-method(原dao的方法)     参数3-args(原dao方法的参数)
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //代理代替原来DAO做的事情---具体的事情
                //  调用当前SqlSession类中的执行方法-------根据原来DAO方法的注解类型来确定 调用什么
                //思路：解析原来DAO方法上的注解(注解的类型---调用方法，注解里面的SQL--调用方法的时候传参)
                //1.获取方法上面的注解对象
                Annotation annotation = method.getAnnotations()[0];
                //2.获取注解对象对应的类型-----为了确定调用SqlSession中的哪个方法
                Class annotationType = annotation.annotationType();
                //3.顺便解析注解对象里面value方法传递过来的sql(获取方法对象) 注解上的：String value();
                Method valueMethod = annotationType.getMethod("value");
                //可以理解为执行注解上的方法
                String sql = (String) valueMethod.invoke(annotation);
                //调用方法的时候，obj参数可能是null值，做一个处理
                Object obj = args == null ? null : args[0];
                //通过annotationType判断，该调用的是SqlSession中的哪个方法
                if (annotationType == Insert.class) {
                    SqlSession.this.updateUser(sql, obj);
                } else if (annotationType == Delete.class) {
                    SqlSession.this.updateUser(sql, obj);
                } else if (annotationType == Update.class) {
                    SqlSession.this.updateUser(sql, obj);
                } else if (annotationType == Select.class) {
                    //查询的方法会有两种的情况：查询一条数据，查询多条数据
                    //通过分析方法的返回值类型进行判定：1、如果原来DAO中的方法返回值是List 2、多条  不是多条obj-domain/map/var
                    Class methodReturnTypeClass = method.getReturnType();
                    //1、如果原来DAO中的方法返回值是List
                    if (methodReturnTypeClass == List.class) {
                        //如果是查询多条记录，此时的方法返回值类型应该是一个List
                        //需要将集合里面的泛型的类型获取出来，传递给下面的方法执行
                        //a.获取方法返回值的全部内容
                        Type returnType = method.getGenericReturnType(); //java.util.List<domain.User>
                        //b.将这个type类型还原为可以操作泛型的那个类型
                        ParameterizedTypeImpl realReturnType = (ParameterizedTypeImpl) returnType;
                        //c.获取realReturnType中的泛型类 有可能范型的返回值是多个 比如 Map<String,String>
                        Type[] patternTypes = realReturnType.getActualTypeArguments();
                        //d.获取patternTypes数组的第一个元素即可
                        Type patternType = patternTypes[0];
                        //e.将patternType还原成原来的Class即可
                        Class resultType = (Class) patternType;
                        //调用实际的方法进行操作
                        return SqlSession.this.selectList(sql, obj, resultType);
                    } else {
                        //单条数据
                        return SqlSession.this.selectOne(sql, obj, methodReturnTypeClass);
                    }
                } else {
                    System.out.println("自定义异常，告知没有这个注解类型，框架处理不了");
                }
                return null;
            }
        };
        //创建一个代理对象
        Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        //将代理对象返回
        return (T) proxyInstance;

    }


}
